﻿using gt.rediscachemanager.Core.ClientServer;
using gt.rediscachemanager.Entry;
using gt.rediscachemanager.Utility;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;

namespace gt.rediscachemanager.Handlers
{
    internal class MonitorHandler
    {
        private ConcurrentDictionary<string, string> m_unAvailableNodeContainer = null;//key nodeName  value:poolName
        private int m_delayedRecoverySeconds;//延迟恢复时间
        private int m_redisFailoverWaitSeconds;//主从切换等待时间
        private string m_clientName = string.Empty;
        private string m_poolName = string.Empty;

        public event EventHandler<MonitorConnectionFailedEventArgs> Monitor_ConnectionFailedEvent;
        public event EventHandler<MonitorConnectionRestoredEventArgs> Monitor_ConnectionRestoredEvent;

        public MonitorHandler(string clientName, int? delayRecoverSeconds, int? failoverWaitSecond)
        {
            this.m_clientName = clientName;
            this.m_delayedRecoverySeconds = delayRecoverSeconds == null ? -1 : delayRecoverSeconds.Value;
            this.m_redisFailoverWaitSeconds = failoverWaitSecond == null ? 10 : failoverWaitSecond.Value;
        }

        public void Monitor(RedisClientServerPool pool)
        {
            m_unAvailableNodeContainer = new ConcurrentDictionary<string, string>();
            m_poolName = pool.Name;
            foreach (var cs in pool.GetAllRedisClientServer())
            {
                cs.ClientServer_ConnectionFailedEvent += Cs_ClientServer_ConnectionFailedEvent;
                cs.ClientServer_ConnectionRestoredEvent += Cs_ClientServer_ConnectionRestoredEvent;
                if (!cs.Connection.IsConnected)
                    MakeClientServerConnectionFailed(cs);
            }
            LogUtility.Warn(string.Format("redisclient:{0} cachepool:{1}  start monitor.", m_clientName, pool.Name));
        }

        private void Cs_ClientServer_ConnectionRestoredEvent(object sender, ClientServerConnectionRestoredEventArgs e)
        {
            var cs = sender as RedisClientServer;
            if (!m_unAvailableNodeContainer.ContainsKey(cs.Node.Name)) return;
            try
            {
                if (e.IsMasterServer && m_delayedRecoverySeconds > 0)
                {
                    Thread.Sleep(this.m_delayedRecoverySeconds * 1000);
                    string name = null;
                    m_unAvailableNodeContainer.TryRemove(cs.Node.Name, out name);
                    LogUtility.Warn(string.Format("redisclient:{0} cachepool:{1} redisnode:{2} restored,remove from unvailable list.", m_clientName, m_poolName, cs.Node.ToString()));
                    Monitor_ConnectionRestoredEvent?.Invoke(this, new MonitorConnectionRestoredEventArgs() { CachePoolName = m_poolName, ClientName = m_clientName, Node = cs.Node });
                }
            }
            catch (Exception ex)
            {
                LogUtility.Error("Handler Cs_ClientServer_ConnectionRestoredEvent error.", ex);
            }
        }

        private void Cs_ClientServer_ConnectionFailedEvent(object sender, ClientServerConnectionFailedEventArgs e)
        {
            var cs = sender as RedisClientServer;
            try
            {
                if (e.IsMasterServer) //not handle if slave connect failed
                {
                    var downTime = DateTime.Now;
                    bool isOk = false;
                    //wait some time for sentinel handler failover  
                    while ((DateTime.Now - downTime).TotalSeconds <= this.m_redisFailoverWaitSeconds)
                    {
                        //get current master server
                        var newMasterEndPoint = cs.Connection.GetEndPoints().FirstOrDefault(x =>
                        {
                            var server = cs.Connection.GetServer(x);
                            return !server.IsSlave && server.IsConnected;
                        });
                        if (newMasterEndPoint != null)
                        {
                            isOk = true;
                            LogUtility.Warn(string.Format("redisclient:{0} redisnode:{1} failover success,master is {2}",
                                    m_clientName, cs.Node.ToString(), RedisClientUtility.GetHostWithPort(newMasterEndPoint)));
                            break;
                        }
                        Thread.Sleep(10);
                    }

                    if (!isOk)
                    {
                        m_unAvailableNodeContainer.TryAdd(cs.Node.Name, m_clientName);
                        LogUtility.Warn(string.Format("redisclient:{0} cachepool:{1} redisnode:{2} connect failed,put this node to unavailable list.", m_clientName, m_poolName, cs.Node.ToString()));
                        Monitor_ConnectionFailedEvent?.Invoke(this, new MonitorConnectionFailedEventArgs() { CachePoolName = m_poolName, ClientName = m_clientName, Node = cs.Node });
                    }
                }
            }
            catch (Exception ex)
            {
                LogUtility.Error("Handler Cs_ClientServer_ConnectionFailedEvent error.", ex);
            }
        }

        public bool CheckAvailable(RedisClientServer cs)
        {
            if (m_unAvailableNodeContainer == null) return true;

            return !m_unAvailableNodeContainer.ContainsKey(cs.Node.Name);
        }

        public void MakeClientServerConnectionOK(RedisClientServer cs)
        {
            string name = null;
            m_unAvailableNodeContainer.TryRemove(cs.Node.Name, out name);
            LogUtility.Warn(string.Format("make redisclient:{0} redisnode:{1} restored,remove from unvailable list.", m_clientName, cs.Node.ToString()));
            Monitor_ConnectionRestoredEvent?.Invoke(this, new MonitorConnectionRestoredEventArgs() { CachePoolName = m_poolName, ClientName = m_clientName, Node = cs.Node });
        }
        public void MakeClientServerConnectionFailed(RedisClientServer cs)
        {
            bool flag = m_unAvailableNodeContainer.TryAdd(cs.Node.Name, m_clientName);
            LogUtility.Warn(string.Format("make redisclient:{0} redisnode:{1} connect failed,put this node to unavailable list.", m_clientName, cs.Node.ToString()));
            Monitor_ConnectionFailedEvent?.Invoke(this, new MonitorConnectionFailedEventArgs() { CachePoolName = m_poolName, ClientName = m_clientName, Node = cs.Node });
        }
    }
}
